A Wi 您所在的位置:网站首页 esp32c3 spiffs A Wi

A Wi

#A Wi| 来源: 网络整理| 查看: 265

 Part 1 - Demonstration Projects 

Turning a LED on and off was the leitmotif of my first post about the XIAO ESP32C3. A connected LED was controlled with a physical push button connected to the development board, then with a button on a Web page served by the XIAO running MicroPython and finally with Bluetooth. The GitHub repository associated with that post also contained two further examples where the LED was controlled from a Web page using two different C++ libraries to run the web server. I now want to expand on this theme. In essence, the XIAO ESP32C3 will be used as the basis of a Wi-Fi switch integrated into my home automation system.

The source code for the PlatformIO projects / Arduino sketches presented in this post can be found in a GitHub Repository: sigmdel/xiao_esp32c3_wifi_switch.

Table of Content The Goal Hardware Hardware Abstraction Tiny Sketch, Big Footprint Magic Ingredients Compiling, Uploading, and Running the Firmware In the PlatformIO IDE In the Arduino IDE Execution of the Firmware Integration with Domoticz using HTML Useful Domoticz Additions Timed Light Auxiliary Light Night Light Perceived Humidity Thoughts on the XIAO Starter Kit Alternate Hardware Critique and Future Developments The Goal

The XIAO ESP32C3 Wi-Fi switch should resemble the venerable Sonoff Basic except that it has built-in sensors: one that measures temperature and humidity and another that measures the ambient brightness or light level. The Wi-Fi switch will run a Web server, specifically ESPAsyncWebServer in order to handle more than one connection, and it will be integrated in the Domoticz home automation system. The diagram below gives an overview of those components.

Any resemblance between the Kitchen Light web interface and the (edited) Tasmota web interface next to it is not accidental. The objective is to try to reproduce the basic functionality of Theo Arends' powerful software which is used in many devices on our home automation system. That is an ambitious goal and if it's reached at all it will be with the culmination of many more posts. The immediate goal in this first step is getting together something that works acceptably.

It's not just a matter of toggling a LED on and off with a button on a Web page; numerous examples can be found on the Web. Whenever the state of the light is changed locally with the button, the light's status has to be updated on the Web page displayed by all clients connected to the Web server and in the home automation system. Similarly, if the toggle button on a client's Web page is clicked, then the hardware controlling the light must be activated accordingly and the light's status must be updated in the home automation system and on all connected clients' Web page simultaneously. Likewise, if the virtual light switch in the home automation system is toggled on or off, the actual relay on the Wi-Fi switch must be updated and the new status of the light must be shown on all connected clients' Web pages.

Hardware

Here is a description of the hardware used in this project.

Obviously, to run ESPAsyncWebServer, an Espressif ESP microcontroller based development board must be used. I chose the XIAO ESP32C3 which uses the RISC-V single-core ESP32-C3 for the simple reason that I have been investigating it lately. An LED with a current limiting resistor. Most dev boards have a built-in LED, the XIAO ESP32C does not. The LED represents the relay that in turns controls a light bulb. A normally open push-button switch. Again many dev boards have a built-in user push-button switch. The XIAO ESP32C3 does have one, (the tiny tactile button labelled B) but I did not use it. A temperature and humidity sensor. This could be a DHT11, DHT22, or DHT20. Actually just about any sensor could be used as long as there is an ESP32 library for it. In a pinch, the sensor can be simulated, meteorological measurements is not the point of this post. A light level sensor. Since there is no requirement for precise measurement of the ambient light level, a light dependant resistor (LDR) could be used. Again, it is possible to simulate this sensor during the development of the software.

In my first build of the project, I used discrete components which happened to be on hand. Then I remembered that Seeed Studio had kindly sent me a XIAO Starter Kit which contains an XIAO Expansion Base which is a carrier board for XIAO form factor developments boards. The Kit contained everything needed for this project and more. Given how easy it was to connect everything together, I decided to use the Kit build in this initial description of the hardware used. A description of the initial build with discrete components is relegated to the penultimate section of this post.

The base has an on-board user push button switch connected to the D1 pin of the XIAO dev board. Among many other things, the Starter kit included other devices that made it simple to build the hardware for the project.

A Grove - Temperature&Humidity Sensor(DHT20). Because it uses an I²C interface, it can be plugged into either of the I²C Grove connectors on the base. A Grove - Light Sensor v1.2 (LS06-S Photodiode). This is an analog sensor which emits an output voltage that increases from 0 to Vcc as the brightness of the ambient light increases. It can be connected to the A0-D0 Grove connector. A Grove - LED Pack on-board potentiometer with red, green, blue, and white LEDs also known as the LED Socket kit. There's a small two-pin female header on board into which a LED can be plugged in (anode + or long lead into the female header labelled +). A potentiometer sets the LED brightness. Start with the pot turned fully counterclockwise for the brightest light. A slight clockwise rotation will quickly diminish the visibility so the LED is not visible over most of the range of the potentiometer. As the name suggests, the signal at the Grove connector on the small board should be wired to the D0 pin of the XIAO ESP32C3 through the A0-D0 Grove connector on the XIAO base. However that connector is used for the light sensor, so the LED was connected to I/O pin D10 of the XIAO ESP32C3. Numerous (7) Grove cables to connect these accessories to the base. Three were used here. Two cables were used with connectors A0-D0 and I²C on the base. The third Grove cable was sacrificed replacing the connector at one end with four male Dupont connectors to make connections with the headers parallel to the XIAO on the expansion base. A USB cable with a Type C connector at one end to connect to the XIAO and the appropriate connector at the other end to connect to the desktop or portable computer. The kit comes with a very short 20 cm (8") Type A to Type C cable which is just fine for my situation. I encountered a problem that was related to the USB connection. At first it seemed as if I had somehow destroyed the Wi-Fi capabilities of the XIAO ESP32C3 when soldering the header pins in order to connect it to the base. It was possible to download sketches to the XIAO, the latter could read the sensor, button, and flash the LED, but it would refuse to connect to the Wi-Fi network. It turns out that the Type C connector was very tight and I had not pushed it in far enough. This was probably an isolated case related to the connector on the XIAO itself, because I was able to use the USB cable from the kit with other XIAO's without any problem and the XIAO with soldered headers would not connect to the Wi-Fi network with a cable from another manufacturer. Again I was probably not pushing its Type C connector hard enough into the XIAO USB-C connector. Hardware Abstraction

Whenever the Web interface is updated, the data displayed is obtained from four Strings in main.cpp.

// Values to be displayed in Web page String ledStatus = "OFF"; String Temperature = "21.8"; String Humidity = "38.9"; String Light = "51";

Of course, the values of these strings depend on the sensor values. I decided to create a hardware abstraction layer to take care of the details of reading the sensor values and updating the corresponding strings. This is the pertinent part of the hardware header file (hardware.h).

// Hardware abstraction void initHardware(void); // Initialize the hardware (LED, button, temperature and light sensors) void checkHardware(void); // Read button and sensors and update readings strings in main.cpp void toggleLed(void); // Toggle the LED state and update ledStatus in main.cpp

That is pretty sparse and hopefully clear. The initHardware() function, called once in the setup() routine, does what one expects given its name. It initializes the two sensors and puts the LED in an initial off state. The checkHardware() function should be called in each iteration of the main loop() routine of the sketch. Every time called, it checks for a button press, and if one has occurred, it calls on toggleLed() to swith the state of the LED from on to off or vice versa. The toggleLed() function also updates the ledStatus String in main.cpp. The toggleLed() function is also used by the web server in an HTTP request that signals that the Toggle button has been clicked.

The details of the handling the hardware are hidden in the hardware.cpp implementation file. Here is how the LED (representing a relay) is handled.

// LED (i.e. relay) void setLed(int value) { digitalWrite(LED_PIN, value); ledStatus = (value ? "ON" : "OFF"); } void toggleLed(void) { setLed(1-digitalRead(LED_PIN)); } void initLed(void) { pinMode(LED_PIN, OUTPUT); setLed(0); }

The function setLed() does most of the work, and there's not much to it! It sets the digital I/O pin connected to the LED to 1 (HIGH) or 0 (LOW) according to its value parameter and then it updates the ledStatus string in main.cpp. Toggling the LED, is just a matter of reading the state of the I/O pin connected to the LED and then inverting its value. This is done in response to a user action, so there is no timing consideration involved. Initializing the LED, is just setting the mode of the I/O pin connected to the LED to OUTPUT and ensuring that the LED is off.

The push button is simpler to handle because the mdSimpleButton library is used. Initialization is implicit; it is done when the button instance of the mdSimpleButton class is created.

// Button mdSimpleButton button = mdSimpleButton(BUTTON_PIN); void checkButton(void) { if (button.update() >= BUTTON_RELEASED) { toggleLed(); } }

The state of the button is checked at every iteration of the loop() function, in order to have a best response rate as close to a wall light switch as possible.

An appropriate library takes care of the details of reading the temperature and humidity sensor. There is no need to read the sensor continuously, or at least I can't conceive of a reason to do that. Consequently, the reading is only done at specified intervals. The minimum time in milliseconds between readings of the sensor is defined in the SENSOR_DELAY macro defined in hardware.h.

// DHT Sensor unsigned long temptime; DFRobot_DHT20 dht20; void initSensor() { while (dht20.begin()) { delay(1000); } temptime = millis(); } void readTemp(void) { if (millis() - temptime > DHT_DELAY) { TempAndHumidity_t tah = dht20.getTempAndHumidity(); Temperature = String(tah.temperature, 1); Humidity = String(100*tah.humidity, 1); temptime = millis(); } }

That initSensor() function is a terrible bit of programming. What happens if the DHT20 is defective? The program could be cought in an endless while loop with no warning at all. The actual code does have logging functions and an error message will be printed to the serial monitor. That's not ideal, but it is better than nothing. Note that the DFRobot_DHT20::getTempAndHumidity method is not part of the original library.

Tiny Sketch, Big Footprint

While the main program is a mere 47 lines of actual code, yet compiled it does take a considerable portion of the Flash memory.

Checking size .pio/build/seeed_xiao_esp32c3/firmware.elf Advanced Memory Usage is available via "PlatformIO Home > Project Inspect" RAM: [= ] 12.4% (used 40516 bytes from 327680 bytes) Flash: [====== ] 58.2% (used 763268 bytes from 1310720 bytes)

The result is slightly different if compiled with the Arduino IDE.

Sketch uses 755444 bytes (57%) of program storage space. Maximum is 1310720 bytes. Global variables use 40452 bytes (12%) of dynamic memory, leaving 287228 bytes for local variables. Maximum is 327680 bytes.

Lest one thinks something is wrong, the XIAO ESP32C3 does make use of its 4 MB of Flash memory. The latter is partitioned into three areas.

RegionSize (MB) Program storage area1.2 Over-the-air buffer1.2 File system (spiffs)1.5 3.9

So there is still plenty of room for a bigger program while ensuring easy over the air updates, but the program is nevertheless very big compared with what Tasmota achieves with the typical 1 MB of Flash memory as found on the Sonoff Basic switch.

Let's look at the source, which as typical starts with the list of libraries that are explicitly included.

#include #include #include #include #include "hardware.h" #include "html.h" #include "secrets.h"

Of course, some of these libraries include many more libraries. The next block of importance in the code has already been seen. It contains the String declarations for the data that will be displayed in the Web interface.

// Values to be displayed in Web page String ledStatus = "OFF"; String Temperature = "21.8"; String Humidity = "38.9"; String Light = "51";

Default values are specified just to ensure that something reasonable is displayed until the actual status of the sensors has been updated. Then the string substitution function is defined. Its role is central to the functioning of this program.

// Template substitution function String processor(const String& var){ if (var == "TITLE") return String("XIAO ESP32C3 WEB SERVER"); if (var == "DEVICENAME") return String("Kitchen Light"); if (var == "LEDSTATUS") return ledStatus; if (var == "TEMPERATURE") return Temperature; if (var == "HUMIDITY") return Humidity; if (var == "LIGHT") return Light; if (var == "INFO") return String("Using AsyncWebServer and a content refresh meta tag"); return String(); // empty string }

The HTML code served to the client contains placeholders which will be replaced by this function just as the HTML page is sent out to a client. Placeholders are "%" quoted strings. For example, the temperature read from the sensor is displayed in a cell of a table in the HTML code send to the client. The placeholder %TEMPERATURE% indicates where the numerical value is to be inserted.

Temperature:%TEMPERATURE% °C ...

When the Web server is sending out the HTML file which contains that segment shown below, it recognizes the placeholder (because of the "%" quotes) and consequently it calls on the processor function with var = TEMPERATURE. The function will then return the Temperature String to the Web server that will substitute the content of Temperature, as last updated by the hardware, into the file being served to a client. This is called template processing.

In the next block of code, an instance of the web server is created, and then the setup() function initializes the Serial peripheral and the hardware. It then goes on to initialize the Wi-Fi radio and connects to the Wi-Fi network, because no Wi-Fi connection means no web server.

// Webserver instance using default HTTP port 80 AsyncWebServer server(80); void setup() { Serial.begin(); // ESP_LOGx use Serial, so a 2 second delay delay(2000); // should be sufficient for USB serial to be up initHardware(); // Connect to the Wi-Fi network, credentials in "secrets.h" WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(100); } // Print local IP address Serial.print("WiFi connected, IP address: "); Serial.println(WiFi.localIP()); // Setup async web browser server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", html_index, processor); }); server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request){ toggleLed(); request->send_P(200, "text/html", html_index, processor); // updates the client making the request only }); server.onNotFound([] (AsyncWebServerRequest *request) { request->send_P(404, "text/html", html_404, processor); }); // Start async web browser server.begin(); }

The end of the setup() function is all about configuring the web server and that amounts to defining three type of HTTP request handlers.

URIResponse http:/// or http://Sends to the client the HTML page html_index defined in html.h http:///ledToggles the state of the LED (using toggleLED which updates ledStatus) Sends to the client the HTML page html_index defined in html.h Anything elseSends to the client the error HTML page html_404 defined in html.h

The rest of the program is the repeating loop() which does nothing but check on the hardware to update the sensor data and the state of the LED.

void loop() { checkHardware(); }

The async web server is running in the background, handling client requests as they come in and does not need to be "pumped" in the loop() function.

Magic Ingredients

This is the char constant that holds the HTML that will be sent by the web server in response to a known request.

#include const char html_index[] PROGMEM = R"rawliteral( %TITLE% html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center; background-color: white;} h1{color: #0F3376; padding: 2vh;} table{margin-left: auto; margin-right: auto; margin-top:20px;} td{font-size: 1.5rem; text-align: left; padding: 8px;} .state{font-size: 2rem; font-weight: bold;margin-top:28px;} .button{display: inline-block; background-color: blue; border: none; border-radius: 6px; color: white; font-size: 1.5rem; width: 5em; height: 3em; text-decoration: none; margin: 2px; cursor: pointer;} .info{margin-top:48px;} %DEVICENAME% Temperature:%TEMPERATURE% °C Humidity:%HUMIDITY% % Brightness:%LIGHT% %LEDSTATUS%

Toggle

%INFO% )rawliteral";

Don't forget that placeholders will be replaced as the HTML code is served. So this is the HTML code received by a web client.

XIAO ESP32C3 WEB SERVER html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center; background-color: white;} h1{color: #0F3376; padding: 2vh;} table{margin-left: auto; margin-right: auto; margin-top:20px;} td{font-size: 1.5rem; text-align: left; padding: 8px;} .state{font-size: 2rem; font-weight: bold;margin-top:28px;} .button{display: inline-block; background-color: blue; border: none; border-radius: 6px; color: white; font-size: 1.5rem; width: 5em; height: 3em; text-decoration: none; margin: 2px; cursor: pointer;} .info{margin-top:48px;} Kitchen Light Temperature:20.7 °C Humidity:36.0 % Brightness:70 OFF

Toggle

Using AsyncWebServer and a content refresh meta tag

There is not much to it. A few CSS styles to make the display pretty, a header, a table with three rows showing the current sensor values, a large ON or OFF showing the status of the LED and a form button labelled Toggle. Finally, there is an information line that will help distinguish various versions of this firmware. In the section there are three meta tags that are of some importance.

Boilerplate code which improve layout on smart phones and tablets. Displays non US-ASCII characters. There is one in the text: "°". This is the magic sauce. It instructs the web client to request a new copy of the HTML page every five seconds.

Given the AsyncWebServer template substitution mechanism discussed above, this ensures that the page displayed by a web client is never more than 5 seconds out of date. It does not matter how many clients are connected to the server, each will request the page every five seconds on its own.

Compiling, Uploading, and Running the Firmware

Once the source code for this project has been downloaded and installed on a desktop machine or portable computer, the following directory structure will be in place. It is designed to make it possible to use either the Arduino IDE or the PlatformIO IDE.

wifi_switch ├── 01_simplified_hdw_version │   ├── includeDFRobot_DHT20 │   │   └── README │   ├── platformio.ini │   └── simple_wifi_switch │   ├── hardware.cpp │   ├── hardware.h │   ├── html.h │   ├── main.cpp │   ├── secrets.h.template │   └── simple_wifi_switch.ino ├── 02_basic_wifi_switch │   ... │   └── libraries ├── AsyncTCP ├── DFRobot_DHT20 ├── ESPAsyncWebServer ├── mdSimpleButton └── SimpleDHT

The project as described above is in the first subdirectory 01_simplified_hdw_version, but to make it as self-contained as possible third-party libraries are also included in the libraries directory. Actually, a couple of libraries found there are modified versions of the currently available repository.

DFRobot_DHT20: I added a type and a function, TempAndHumidty_t DFRobot_DHT20::getTempAndHumidity(), to the library to access the DHT20 only once when getting the temperature and humidity readings instead of twice if one were to use the separate functions found in the library. ESPAsyncWebServer: I could not find a version of this library compatible with the ESP32-C3 variant of the Arduino-ESP32 core. The PlatformIO library manager adds the version 1.2.3 of the ESP Async WebSever library by Hristo Gochckov found here https://github.com/me-no-dev/ESPAsyncWebServer which is already 4 years old. It is incompatible with the latest version of md5.h in the Arduino-ESP32 core. The development branch of the library does contain a solution but there remains a version issue which prevents the PlatformIO library manager from getting the newer version. The Arduino library manager installs a fork by dam74 (dvarrel) found here https://github.com/dvarrel/ESPAsyncWebSrv which is currently at version 1.2.6 which does get around the problem with the upstream repository. Unfortunately, the sketch will not compile with either of these newest versions. The issue and the solution have been identified ( Compile error for ESP32 C3- based boards #1164), but the change has not been committed in the repositories. So the solution adopted here is to include a corrected version of the latest me-no-dev code in the libraries directory alongside other libraries that were either modified or not readily available using the library manager of either the Arduino IDE or PlatformIO.

Looking at the source code in simple_wifi_switch, three have already been covered main.cpp, hardware.h and html.h. The WiFi library handles connection with a Wi-Fi network but it needs the network's credentials which it expects to find in a file named secrets.h file not included in the source code. Instead there is a file named secrets.h.template.

// Replace with the correct network credentials const char* ssid = "***Wi-Fi***Network***Name***"; const char* password = "***Wi-Fi***Network***Password***";

As instructed edit the file to provide the Wi-Fi network credentials and save it as secrets.h.

The sketch, simple_wifi_switch.ino, is almost nothing but comments. It is there because the Arduino IDE expects a sketch file with the same name as the directory in which it is situated. The Arduino IDE treats .ino files differently from other source files, in particular, by generating a temporary header file. PlatformIO can work with .ino files so presumably, the content of main.cpp could be in simple_wifi_switch.ino and both IDE would be able to compile the source. I prefer avoiding this non standard behaviour which I find confusing given my limited experience with C/C++.

Instead of using Serial.print() statements throughout the code, I decided to use the logging facility built into Arduino-ESP32. Not only does that look very professional, but it has the advantage of removing all the associated code from the binary when the log level is set to 0 or none at a later date, without having to change the source code at all. You might want to look at the debug code in DFRobot_DHT20 which achieves the same thing in a universal fashion meaning it does not depend on a particular platform.

In the PlatformIO IDE

To open the project in PlatformIO, go to the PIO home page, click on the Open Project button and then browse to the 01_simplified_hdw_version directory that contains the platformio.ini configuration file for the project and then click on the Open "01_simplified_hdw_version" button to load the project.

[platformio] ; Make the Arduino IDE happy (.INO file must be in a directory of the same name) src_dir = simple_wifi_switch lib_dir = ../libraries [env:seeed_xiao_esp32c3] board = seeed_xiao_esp32c3 framework = arduino platform = espressif32 monitor_speed = 460800 ; Enable ArduinoEP32 logging build_flags = -DCORE_DEBUG_LEVEL=5 ; Clean the project after changing the level

There are a few things that are unusual about the content of the platformio.ini configuration shown above.

src_dir = simple_wifi_switch Normally, the main.cpp file of a PlatformIO project is in the src dirctory alongside the platformio.ini configuration file for the project. But that clashes with the Arduino contraint that the sketch must be in a directory with the same name as itself except for the .ino extension. The way around this is to explictly change the source directory in PlatformIO as done here with the src_dir directive. lib_dir = ../libraries Similarly, PlatformIO expects to find private libaries is a specific location, but it is incompatible with Arduino. The solution is to explictly change the local libraries directory in PlatformIO as done here with the lib_dir directive. monitor_speed = 460800 The baud of the serial port does not matter, because a USB CDC interface is used. I just set a very high speed here to no hobble the terminal emulator used by PlatformIO. Debug Levels ValueMeaning 0None 1Error 2Warn 3Info 4Debug 5Verbose build_flags = -DCORE_DEBUG_LEVEL=5 That build flag and an #include #include "esp32-hal-log.h" in a module is all that is needed to enable logging in that module. Here the log level has been set to Verbose. Not only will the log messages from the sketch be displayed, but log messages from other libraries will also be displayed.

Make sure that a recent version of the Espressif 32 platform is used. It should be version 6.1.0 or newer, otherwise PlatformIO may have problems finding the XIAO ESP32C3 when uploading the firmware.

The project is compiled with the PlatformIO: Build command and then uploaded to the microcontroller with the PlatformIO: Upload command. Once the upload is completed successfully, the ESP32-C3 will start executing the new firmware. The serial output can be seen in a terminal. PlatformIO: Serial Monitor will start a terminal session and connect to the XIAO. Most times when the upload fails, it's because I have a serial monitor opened and connected with the XIAO. I just need to close all the open sessions and try the upload again.

There are many ways to execute PIO commands, the easiest is to click on the appropriate button in the button bar found at the bottom of the window.

In the Arduino IDE

As far as I know, the Arduino IDE does not support configuration files for individual sketches. The setup is a manual thing. Installing the Espressinf Arduino-ESP32 platform is a two-step procedure.

Add the URL of the JSON index file of the arduino-esp32 package, https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json in the Additional boards manager URLs: list in the Settings tab of the application Preferences found in the File menu. It is simpler than described as the image below attests.

Details can be found under Software setup in the Getting Started Seeed Wiki. Make sure that esp32 by Espressif is installed. This is done with the BOARDS MANAGER which is found under the Tools/Board:.../Boards manager... menu. There is an icon that can be clicked instead of going through the menu, it is the one highlighted in the column of icons on the left of the IDE window on the screen capture to the right.

There should be no need to repeat these steps, it is a one-time operation. The IDE needs to be told about the location of the sketch otherwise it will not find the libraries directory. This is done in the Preferences. Click on the BROWSE button and navigate to the code_switch directory so as to fill in the Sketchbook location: field as shown in the image below.

It would be good to note the previous value of the field in order to restore it when no longer working with this xiao_esp32c3_project2.

There are two configuration steps that are required for each sketch as it is loaded into the IDE.

Select the board in the Tools/Board menu. Be patient, there are many, many ESP32 based boards and they do not seem to be arranged in any meaningful way. Select the serial port of the board in the Tools/Port: menu. Of course the actual selected port can differ from what is shown on the right. Just pick the correct port from the list of all found ports displayed when the menu item is clicked.

Finally, the debug level for the logging done by the sketch is set in the simple_wifi_switch.ino file.

Debug Levels ValueMeaning 0None 1Error 2Warn 3Info 4Debug 5Verbose #if (!PLATFORMIO) // Enable Arduino-ESP32 logging in Arduino IDE #ifdef CORE_DEBUG_LEVEL #undef CORE_DEBUG_LEVEL #endif #ifdef LOG_LOCAL_LEVEL #undef LOG_LOCAL_LEVEL #endif #define CORE_DEBUG_LEVEL 5 #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #endif

Just set the CORE_DEBUG_VALUE to the desired level. Once all the configuration is completed, compiling the code can be done by clicking on the check mark icon on the top tool bar. Uploading to the XIAO is done by clicking on the right arrow icon in the tool bar. Note that uploading will compile the code if need be. Starting the serial monitor once the code has been uploaded is done by clicking on the looking-glass icon at the right edge of the tool bar. There are keyboard short cuts and menu items to do these operations.

Execution of the Firmware

It is easier to capture the start of the program when the XIAO is reset in PlatformIO because it's serial monitor reconnects automatically. Each time the XIAO is reset, the serial connection is broken and the Arduino IDE disconnects the serial monitor and clears the Port setting. Before restarting the serial monitor the port has to be reset manually in the Tools manu and I am not nimble enough to do this in time to see the logging message unless a long delay were to be introduced after the Serial.begin() in the setup() function.

Here is a the logging output in from the firmware in the PlatformIO serial terminal along with comments in italic. First let's look at the start-up events.

Reconnecting to /dev/ttyACM1 . Connected! Function setup() started with Serial.begin() and then a 2 second delay. Meaning the internal setup time is approximately 0.16 seconds. [ 2162][I][main.cpp:44] setup(): [MAIN] Connecting to COROBRUN-2 [ 2169][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 0 - WIFI_READY [ 2201][V][WiFiGeneric.cpp:340] _arduino_event_cb(): STA Started [ 2202][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 2 - STA_START [ 2204][V][WiFiGeneric.cpp:97] set_esp_interface_ip(): Configuring Station static IP: 0.0.0.0, MASK: 0.0.0.0, GW: 0.0.0.0 [ 2260][V][WiFiGeneric.cpp:355] _arduino_event_cb(): STA Connected: SSID: COROBRUN-2, BSSID: ec:be:dd:e6:ac:be, Channel: 1, Auth: WPA2_PSK [ 2261][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 4 - STA_CONNECTED [ 2316][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 2416][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 2516][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 2616][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection ... [ 3316][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3416][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3516][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3616][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3716][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection [ 3789][V][WiFiGeneric.cpp:369] _arduino_event_cb(): STA Got New IP:192.168.1.162 [ 3790][D][WiFiGeneric.cpp:931] _eventCallback(): Arduino Event: 7 - STA_GOT_IP [ 3793][D][WiFiGeneric.cpp:996] _eventCallback(): STA IP: 192.168.1.162, MASK: 255.255.255.0, GW: 192.168.1.1 [ 3816][I][main.cpp:49] setup(): [MAIN] Waiting for Wi-Fi connection The only Serial.print() statement in the sketch provides an easy way to identify the URL of the Web server: WiFi connected, IP address: 192.168.1.162 Note that it took a bit more than 1.5 second to establish to connect to the Wi-Fi network. This is by no means a constant. [ 3817][I][hardware.cpp:22] initLed(): [HDW] Initializing LED I/O pin. [ 3818][I][hardware.cpp:14] setLed(): [HDW] LED now OFF. [ 3823][I][hardware.cpp:44] initSensor(): [HDW] Initializing DHT20 temperature and humidity sensor. [ 3931][I][esp32-hal-i2c.c:75] i2cInit(): Initialising I2C Master: sda=6 scl=7 freq=100000 [ 3943][I][main.cpp:76] setup(): [MAIN] setup completed. The sensors are polled at regular intervals and their readings are updated. First reading of the temperature and humidity sensor 2 seconds after the hardware has been initialized. [ 5948][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [ 6009][I][hardware.cpp:55] readTemp(): [HDW] Temperature 21.8 --> 18.9 [ 6010][I][hardware.cpp:57] readTemp(): [HDW] Humidity 38.9 --> 40.6 First reading of the light sensor, about 5 (SENSOR_DELAY/2) seconds later. This is a first use of the analog digital converter, note how the later is calibrated. [ 10943][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [ 10943][D][esp32-hal-adc.c:190] __analogReadMilliVolts(): eFuse Two Point: Supported [ 10947][I][esp32-hal-adc.c:232] __analogReadMilliVolts(): ADC1: Characterized using Two Point Value: 0 [ 10956][I][hardware.cpp:76] readLight(): [HDW] Light 51 --> 70 Temperature and humidity sensor read again, (SENSOR_DELAY) 10 seconds after last reading. [ 16012][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [ 16073][I][hardware.cpp:55] readTemp(): [HDW] Temperature 18.9 --> 18.9 [ 16073][I][hardware.cpp:57] readTemp(): [HDW] Humidity 40.6 --> 40.6 No calibration of the ADC from now on. [ 20962][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [ 20962][I][hardware.cpp:76] readLight(): [HDW] Light 70 --> 71 [ 26076][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [ 26137][I][hardware.cpp:55] readTemp(): [HDW] Temperature 18.9 --> 18.9 [ 26137][I][hardware.cpp:57] readTemp(): [HDW] Humidity 40.6 --> 40.4

Now let's look at the use of the physical push-button and the Web interface to control the LED/relay.

The physical push button clicked and was released and the LED is turned on by the button handler. [1169852][I][hardware.cpp:33] checkButton(): [HDW] Push-button released [1169853][I][hardware.cpp:14] setLed(): [HDW] LED now ON. [1173372][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [1173433][I][hardware.cpp:55] readTemp(): [HDW] Temperature 19.2 --> 19.2 [1173433][I][hardware.cpp:57] readTemp(): [HDW] Humidity 40.1 --> 40.1 [1173435][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [1173443][I][hardware.cpp:76] readLight(): [HDW] Light 70 --> 73 LED turned off with the physical push button. [1174394][I][hardware.cpp:33] checkButton(): [HDW] Push-button released [1174394][I][hardware.cpp:14] setLed(): [HDW] LED now OFF. A Web browser requests the index (default) page from the Web server. [1333735][I][main.cpp:58] operator()(): [WEB] Index page requested. Reading of sensors continuing. [1334396][V][hardware.cpp:53] readTemp(): [HDW] Reading temperature and humidity sensor [1334457][I][hardware.cpp:55] readTemp(): [HDW] Temperature 19.3 --> 19.3 [1334457][I][hardware.cpp:57] readTemp(): [HDW] Humidity 39.8 --> 39.9 [1334459][V][hardware.cpp:73] readLight(): [HDW] Reading and updating light sensor values. [1334467][I][hardware.cpp:76] readLight(): [HDW] Light 71 --> 71 The Web browser continues to request the index page again every 5 seconds. With the next request, the changed value of the humidity sensor will be displayed in the refreshed Web page. [1338944][I][main.cpp:58] operator()(): [WEB] Index page requested. [1344062][I][main.cpp:58] operator()(): [WEB] Index page requested. The Web code toggle button is clicked and the Web browser sends an HTTP request. [1488544][I][main.cpp:62] operator()(): [WEB] Web button pressed. The Web server request handler toggles the state of the LED. [1488544][I][hardware.cpp:14] setLed(): [HDW] LED now ON. The web server then responds to the HTTP request by sending the index page back to the Web browser which will update the LED ON/OFF indicator on the Web page. Any other connected Web browser will show the updated status of the LED when they send an HTTP request to refresh their copy of the index page. [1493670][I][main.cpp:58] operator()(): [WEB] Index page requested.

At this point we are two thirds of the way through the first version of this project; adding the XIAO ESP32C3 Wi-Fi switch into the home automation system remains to be done.

Integration with Domoticz using HTML

It seems as if everyone is talking about Home Assistant. In just February and March there have been two articles on the Seeed Studio Wiki about using the XIAO ESP32C3 with HA: XIAO ESP32C3 accesses Home Assistant via ESPHome service and Connect Grove Modules to Home Assistant using ESPHome. However, there are other open source home automation systems, among them Domoticz which has been our home automation server for a number of years. I am not trying to convert anyone, but I do think Domoticz has some very good qualities. Among them, there is the simple installation and the very low system requirements. Initially, the system was built around Domoticz running on a single core Raspberry Pi Model B+ v1.2 and the performance was more than adequate. I even prepared a small system on a Raspberry Pi model B (Rev 2.0 211.12) with only 512 MB of memory for my sister. The Web interface might have been slow, but Domoticz itself had no problem controlling IoT devices quickly enough to be transparent.

Below I am assuming that Domoticz is already installed on a computer connected to the local area network. The Domoticz Wiki Main Page has links to installation instructions on the "big three" operating systems and specifif instructions for Raspberry Pis, Docker and a couple of NAS appliances. In Linux and any Raspberry Pi model the procedure is very simple. Enter a one line command at the prompt,

pi@tarte:~ $ sudo bash -c "$(curl -sSfL https://install.domoticz.com)" and then answer the few questions asked. It is usually quite safe to accept the default values, but port assignments should be checked against any installed servers on the system. Of course there is no worry with a fresh install of the operating system on the host machine.

The only thing that is a prerequisite for this post is that the "Dummy" hardware interface be installed.

Open the Domoticz Web interface in a Web browser. The address is http://:8080 where IP is the network address of the computer hosting Domoticz and 8080 is the default TCP port used by Domoticz. If you selected something else during the installation process, adjust accordingly. If you prefer using a secure protocol use the https:// address if the 443 default port was accepted, otherwise add the port after the IP address separated with a colon. A self-signed certificate is included in the Domoticz installation so it will probably be necessary to confirm that an exception can be made to access the site. Starting with the newly released 2023.1 stable version of Domoticz, a username (admin) and password (domoticz) will have to be given. Click on the Setup button and then the Hardware button. In the drop-down list select Dummy (Does Nothing... type and above give the hardware a name. I chose Virtual. Click on the Add button.

Let's try to add the XIAO ESP32C3 Wi-Fi switch with its sensors into the Domoticz environment with the least number of changes to the code presented above. Frankly, there is not much to do. The first step is to create three virtual devices in Domoticz: a virtual switch, a virtual lux meter and a virtual temperature + humidity sensor. To add these virtual devices go back to the HARDWARE page.

Click on theCreate Virtual Sensors button. This brings up the following dialog box.

Select the sensor type, in the first case, Switch and enter a name for the switch. Do the same for the other two devices choosing the correct device type. These three devices will have consecutive identity numbers (idx) if constructed one after the other. In my case they were assigned id numbers 204, 205 and 206.

We are not truly measuring lux and there is a percent % virtual sensor, but I prefer the lux meter icon. Strictly speaking it does not really matter and in daily use the lux meter will be hidden (just by starting the name with a "$") or even replaced with a user variable. In the beginning, though, it will be useful to see the ersatz lux values in order to calibrate the sensor and determine which value represents dusk.

The values displayed on the virtual devices in the Web interface of the home automation system will be done with HTML requests. These must be JSON formatted HTTP requests as explained in the Wiki on Domoticz API/JSON URL's. It is not a bad idea to test with curl to verify that the HTTP request sent to the Domoticz web server does what is desired.

michel@hp:~$ curl "http://192.168.1.22:8080/json.htm?type=command¶m=udevice&idx=204&nvalue=1" -w "\n" { "status" : "OK", "title" : "SwitchLight" } michel@hp:~$ curl -d "type=command¶m=udevice&idx=204&nvalue=0" "http://192.168.1.22:8080/json.htm" -w "\n" { "status" : "OK", "title" : "SwitchLight" } michel@hp:~$ curl "http://192.168.1.22:8080/json.htm?type=command¶m=udevice&idx=504&nvalue=1" -w "\n" { "status" : "ERR" }

The first two requests will respectively set the virtual light switch state to on and off only. Domoticz will not take any action except for changing the displayed state of the virtual switch on the Web interface. While POST request can be used as seen in the second example above, GET requests will be used as they are simpler to make. The third request shows the typical respons when something is wrong with the query, such as an invalid index number.

As in the case of the hardware, I created a simple domoticz library to take care of updating the sensors in the home automation web interface. Here is the header file with the three functions to update each type of sensor.

#pragma once #include "domoticz_data.h" // Update the state of the virtual switch with given idx, value=0 for Off, value=1 for On. bool updateDomoticzSwitch(int idx, int value); // Update the "Lux" level to value in the light sensor with the given idx. bool updateDomoticzLightSensor(int idx, int value); // Update the Temperature (value1) and Humidity (value2) values in the Temp+Humidity // sensor with the given idx. Must set the humidity state to normal (0), comfortabe (1), // dry (2) or wet (3) explicitly, Domoticz does not calculate a value even though // it does calculate the dew point. // // Note value1 must be in °C even if °F are chosen as units in Domoticz settings // value2 must be a percent (from 0 to 100) such as 48.9 and will be displayed as 48.9% bool updateDomoticzTemperatureHumiditySensor(int idx, float value1, float value2, int state=0);

The state parameter will be discussed later under the heading Perceived Humidity.. As it is, the default 0 value will display "normal" in the Web interface no matter the relative humidity. The implementation found in domoticz.cpp is straight forward. The logic is simple, the function handling each type of sensor builds up a URL with the appropriate JSON formatted query. The function sendHttpRequest() takes care of sending that URL and logging the result obtained from the Domoticz web server.

#include #include "esp32-hal-log.h" #include #include #include "domoticz.h" #define TAG "DMZ" bool sendHttpRequest(String url) { if (WiFi.status() != WL_CONNECTED) { ESP_LOGE(TAG, "Domoticiz not updated, Wi-Fi not connected."); return false; } ESP_LOGV(TAG, "HTTP request URL: %s", url.c_str()); HTTPClient http; http.begin(url.c_str()); int httpResponseCode = http.GET(); String payload; if (httpResponseCode > 0) payload = http.getString(); // Free resources http.end(); // Return true if OK with information log message, else return false with an error log message if ( (httpResponseCode == HTTP_CODE_OK) && (payload.indexOf("\"status\" : \"OK\"") > 0) ) { ESP_LOGI(TAG, "Domoticz updated."); return true; } else if (httpResponseCode == HTTP_CODE_OK) { ESP_LOGE(TAG, "Domoticz update failed with response: %s", payload.c_str()); return false; } ESP_LOGE(TAG, "Domoticz update failed with HTTP code: %d", httpResponseCode); return false; } bool updateDomoticzSwitch(int idx, int value) { if (WiFi.status() != WL_CONNECTED) { ESP_LOGI(TAG, "Domoticiz not updated, Wi-Fi not connected."); return false; } String url = "http://"; url += DOMOTICZ_URL; url += "/json.htm?type=command¶m=udevice&idx="; // only update the status, do not ask Domoticz to perform action url += idx; url += "&nvalue="; url += value; return sendHttpRequest(url); } bool updateDomoticzLightSensor(int idx, int value) { String url = "http://"; url += DOMOTICZ_URL; url += "/json.htm?type=command¶m=udevice&idx="; // only update the status, do not ask Domoticz to perform action url += idx; url += "&nvalue=0&svalue="; url += value; return sendHttpRequest(url); } bool updateDomoticzTemperatureHumiditySensor(int idx, float value1, float value2, int state) { String url = "http://"; url += DOMOTICZ_URL; url += "/json.htm?type=command¶m=udevice&idx="; // only update the status, do not ask Domoticz to perform action url += idx; url += "&nvalue=0&svalue="; url += String(value1, 1); url += ";"; url += String(value2, 0); url += ";"; url += state; return sendHttpRequest(url); }

A little bit of care is needed when handling the response from the Domoticz web server. As seen with the curl commands, even when Domoticz receives an incorrectly formatted query, it will respond with a JSON formatted response with a return code of 200 (OK). Consequently the JSON response must be scanned to ensure that "status" : "OK" entry is present. The identity numbers of the virtual sensors and the IP address of the Domoticz web server are defined in the header file domoticz_data.h which is not included in the source code of this project. As with the Wi-Fi credential, there is a template file, domoticz_data.h.template instead.

#pragma once #define DOMOTICZ_URL ":" #define SWITCH_IDX 204 #define LUX_IDX 205 #define TEMP_HUMI_IDX 206

Edit this file entering the correct values and save as domoticz_data.h in the same directory as the template and main.cpp.

It is rather obvious when the virtual sensors should be updated by the XIAO ESP32C3 firmware. It has to be done in the hardware handlers. So there are four extra lines in harware.cpp.

#include "domoticz.h" is added at the top of the file. updateDomoticzSwitch(SWITCH_IDX, value); is added at the end of the setLed(int value) function. updateDomoticzTemperatureHumiditySensor(TEMP_HUMI_IDX, tah.temperature, 100*tah.humidity); is added at the end of the readTemp() function. updateDomoticzLightSensor(LUX_IDX, value); is added at the end of the readLight() function.

So now the Domoticz web interface will be updated each time the LED state is changed or a sensor is read. Indeed, the update on the Domoticz Web interface could occur more quickly than on a client Web browser connected to the XIAO ESP32C3 given that may require up to 5 seconds before that update is displayed. At this point clicking on the virtual switch in the Domoticz Web interface will cause its value to toggle between on and off, but nothing will happen on the XIAO side. Adding actions to be performed when the state of the virtual switch is changed within Domoticz will help solve this problem.

Click on the Edit button of the virtual switch in teh Switches tab. Then add two URLs in the On Action and Off Action fields as shown below. Do not forget to click on the Save button otherwise the changes to fields will not be kept.

To complete this step, the XIAO Web server must include an additional two handlers for the requests that Domoticz may send to it. This is done in the setup() function in main.cpp.

server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){ ESP_LOGI(TAG2, "Domoticz command on"); setLed(1); request->send(200, "text/plain", "Domoticz device on"); }); server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){ ESP_LOGI(TAG2, "Domoticz command off"); setLed(0); request->send(200, "text/plain", "Domoticz device off"); });

Domoticz does not expect much of a response when it executes the On Action or Off Action. However to avoid polluting the Domoticz log, it is necessary to send a non-empty response with a 200 OK HTTP code. The mime type of the response probably does not matter much, but the text cannot be empty, so I chose to send back to the Domoticz server the same message as is logged to the serial monitor. A 202 Accepted code also satisfies Domoticz as long as the response is not empty.

Useful Domoticz Additions

Home automation devices become "intelligent" when decisions are made by the system independent of human intervention. Let's look at a few examples of things that can be done with our simple Wi-Fi device.

Timed Light

With help from Domoticz, the Wi-Fi light can become a timed light. Click on the Edit button of the XIAO ESP32C3 (virtual) Switch. Then put the desired number of seconds during which time the switch will remain on in the Off Delay field. Do not forget to click on the Save button before pressing the Back button.

I have done this for a light in a staircase and something similar for a light in a wardrobe. In both cases the delay was longer than what is shown above but, unless one is really patient, 30 seconds seems like an eternity when testing.

Auxiliary Light

Let's say that the XIAO ESP32C3 is controlling the light in a hallway that goes to a wardrobe which also illuminated with a Wi-Fi controlled light. Instead of putting a timer on the latter, it makes more sense to let the hallway light control the wardrobe light. The nomenclature used in Domoticz to accomplish this is a bit counter-intuitive. Edit the wardrobe sensor, named Penderie in my case, in the Domoticz Web interface. Then select the hallway virtual device (XIAO ESP32C3 Switch) in the Sub/Slave Device: drop down list.

The click on the Add button and the device included in the list of entries above which was empty initially as can be seen above. Again do not forget to click on the Save button before leaving the device edit screen with the Back button or any other means. As I said, this feels wrong because it's the wardrobe light that will follow the lead of the hallway light; the wardrobe light will be turned on or off when the XIAO ESP32C3 hallway light is turned on or off. This is not a reciprocal relationship, so that if there's enough light in the hallway, that light can be left off, and the wardrobe light can be turned on or off without affecting the hallway light. In addition I would set the Off delay of the wardrobe light to half an hour or 45 minutes and the Off delay of the hallway light to a slightly longer delay.

This is not a theoretical discussion. Our house has outside lights controlled with a wall switch beside the front entrance and the attached garage has outside lights controlled by a switch in the garage. If I wanted to illuminate the way for guests returning to their car in the driveway when it was dark, I had to run to the garage to turn its outside lights and then come back to the front door to turn on the house outside lights. When no longer needed I had to retrace my steps to turn off both lights. By establishing a Domoticz sub/slave relationship as described here between two Wi-Fi wall switches controlled by Domoticz, the problem is solved. The switch by the front entrance turns all outside lights on or off. The switch in the garage controls the outside garage lights only.

Night Light

The XIAO ESP32C3 could be the basis of a night light, because Domoticz has built-in functions to do that. Click on the Timers button of the virtual switch.

There are many ways to set the time at which a IoT switch will be turned on or off. In this example, the light will be turned on at dusk every day, 40 minutes before sunset. Note that the action is enabled and that the Add button must be pressed to add the action to the list at the top.

The screen capture above shows the result after adding an Off command to be performed 10 minutes after sunrise. Domoticz calculates the sunrise and sunset times for every day and shows them for the current date under its top banner in every tab sheet except for the settings page. Using the sunset and sunrise times to turn on and off a night light is certainly preferable to using fixed hours which would have to be adjusted over the course of a year. However, with the light sensor on the Wi-Fi switch it is possible to turn the light when needed implicitly taking into consideration the cloud cover and other possible determinants of the actual light level.

Here is a very simple dzVents Lua script to do that.

return { on = { devices = { 205 -- lux meter } }, execute = function(dz, device) if (device.lux

I am not convinced that this is the appropriate way to go. Back in July 2017, I had chosen to use the dew point instead. I may come back to that approach in a future installement.

Thoughts on the XIAO Starter Kit

It was surprising that Seeed Studio had sent a XIAO Starter Kit, I had never expressed an interest in it nor even mentioned it in the few e-mails we have exchanged. Do not misunderstand me, I am not against starter kits as such, I just thought I had outgrown them.

When dipping my toes in the Arduino universe, I did purchase a starter kit from Sparkfun built around their RedBoard, an "official" Arduino clone. At the time, it was comforting to get a development board and a few components that I could trust would work together. However, when working through a few of the examples in the included project book, I was underwhelmed by the experience. Again, I do not want this last statement to be misunderstood. I had experience in programming with Pascal, Modula, Delphi and some Java and I already had a decent collection of parts such as I²C and SPI displays, sensors and so on.

After first building the project with discrete components, I remembered the XIAO Starter Kit and build a second project with it as explained above. This experience has changed my opinion. I can now see the value of the kit for those that have a bit more experience. First of all, the XIAO Expansion Base by itself does transform the XIAO boards into true development boards. There is a built-in I²C display, a buzzer, a "user" push button, a reset switch, a micro SD card reader and a lithium coin cell battery holder. Having all these components so readily available often makes it possible to start developing software without delay. The ability to easily add two other I²C devices and a serial port using Grove compatible peripherals only enhances this. And there's more. As shown above, there is a Grove connector for the D0 I/O pin and female headers for all the pins of the XIAO so that it is easy to connect the base to a breadboard.

There is a downside to using the XIAO Expansion Base. It is necessary to solder male headers on the XIAO board. Sometimes one would not want to do this in case the XIAO is to be soldered to a carrier board using the castellated pads or if connections will be made with wires. Of course, I did solder the header pin on one of my XIAO ESP32C3 and decided to leave it in the XIAO Expansion Base declaring that to be my development test bed for all members of the XIAO family. Given that the XIAO costs a tenth as much as the Starter Kit and a third of the Expansion Base, it is not much of an investment. To use accounting terminology, there is no real sunk cost as the XIAO with pin headers could still be used elsewhere.

In conclusion, I can see two use cases for the XIAO Starter Kit. Beginners will enjoy using it. They will be able to concentrate on learning how to use the Arduino IDE (or better yet the PlatformIO IDE) which is daunting enough at the start without having to worry much about the hardware. For those that are already at ease working with microcontrollers, sensors and so on, then there is a gain in productivity to be had with the kit. The kit is not cheap compared to the XIAO boards, so if that is a problem, perhaps the XIAO Expansion Base would make more sense. In that case, individual Grove sensors and other peripherals could be purchased as needed, although one has to be careful because shipping charges can be proportionally high when making little purchases. No matter, I no longer think that I have outgrown use useful devices such as the XIAO Expansion Base or Starter kits.

Alternate Hardware

As made amply clear that the XIAO Expansion Base and the Grove components are not needed. At first, I used discrete components on a small breadboard and connections to the XIAO with flying leads. The light sensor was a voltage divider made with a light dependant resistor and a fixed resistor and the temperature and humidity sensor was a DHT11 which are notoriously inaccurate. I must admit that the humidity measures of the DHT11 were grossly inaccurate but that was expected because the sensor is one of two rejects from another project. So in actual use, I would recommend the DHT22 or the DHT20 which is more accurate than the DHT11. At the very least get more than one DHT11 and test their accuracy. Here is a schematic of the four components as connected to the XIAO ESP32C3 board.

I used a DHT11 mounted on a small printed circuit board which takes care of the pull up resistor. Be careful the pinout is different on different boards. The value of the current limiting resistor connected to the LED is not critical, 200 to 500 ohms would be acceptable depending on the LED's colour. I know that sounds odd, but the power requirement of LEDs typically depends on their colour; look it up on the Web. I am not sure which LDR I used; the package says "type 5516 5-10K" but I remember that I wrote that months after buying the photoresistor and having a hard time identifying it. A 4.3 kΩ seems well matched to the LDR.

Of course the sensor drivers used previously will not work with this hardware. I rewrote the header and implementation files to accommodate the two sets of peripherals presented above and even other types of sensors. I have even made it possible to substitute emulated sensors instead of physical devices. Here is the modified header file. I also renamed the files hardware2.h and hardware2.cpp to avoid overwriting the original versions.

#pragma once #include // pin definitions // The sensor data in main.cpp that the hardware will update - all strings extern String ledStatus; extern String Temperature; extern String Humidity; extern String Light; // LED (or relay) #define LED_PIN D10 // Push button #define BUTTON_PIN D1 // Light sensor #define LS_PIN D0 // has to be one of D0 - D3 (A0 - A3) // Temperature and humidity sensor #define DHT_PIN D8 // not used if DHT = DHT20 or DHT_NONE #define DHT_NONE 0 // emulate a temperature/humidity sensor #define DHT11 1 // 1-wire DHT11 sensor #define DHT22 2 // 1-wire DHT22 sensor #define DHT20 3 // I²C DHT20 sensor Grove temperature/humidity sensor #define DHT DHT11 // Installed temperature/humidiy sensor type // light sensor #define LS_NONE 0 // emulate light sensor #define LS_LDR 1 // light dependant resistor #define LS_DIODE 2 // photodiode sensor Grove Light Sensor 1.2 #define LS LS_LDR // installed light sensor type #define LS_FIFO 10 // size of FIFO queue when calculating rolling average if < 2, then no averaging done #define LS_READ 10000 // minimum time (in ms) between readings of the sensor data if averaging #define SENSOR_DELAY 60000 // minimum time (in ms) between updates of the sensor data // Hardware abstraction void initHardware(void); // Initialize the hardware (LED, button, temperature and light sensors) void checkHardware(void); // Read button and sensors and update readings strings in main.cpp void toggleLed(void); // Toggle the LED state and update ledStatus in main.cpp void setLed(int value); // Set the LED on (value = 1) or off (value = 0)

As can be seen all this is handled with macros, making the code messier, especially in the implementation part so I will not go into details. I will point out that I added a First-In-First-Out (FIFO) queue to calculate a rolling average for the light level measurements. It seemed appropriate to average the data from the light sensor because a cloud could cover the sun for a short interval, a person could cast a shadow over the sensor for a few seconds, or a temporary light might push up the reading artificially. The light level is read at a faster frequency, specified with the LS_READ millisecond time interval between readings. On each reading, the latest value is added to the queue while the oldest reading is removed and the average value is recalculated and saved. When the SENSOR_DELAY has expired, it is the latest calculated average value which is used to update String Light; displayed in Web clients and in the Domoticz Web interface. The size of the queue is 10 by default but that can be changed with the LS_FIFO. Obviously if LS_FIFO < 2, there will not be much averaging going on!

Critique and Future Developments

While the Wi-Fi switch does work, at this stage, this is just a demonstration project. It is much too brittle. Case in point, when I rebuilt the project with the XIAO starter kit, it did not work properly. The DHCP assigned IP address of the second ESP32-C3 was different because it has a different MAC address. Using fixed IP addresses was not a good idea, akin to using unnamed numerical constants or so-called magic numbers. There are ways around this. The ESP32 does support multicast DNS (mDNS) so that the switch could be assigned a host name which it can advertise. This is not foolproof but it does work, some of the time at least. I find that using an MQTT broker much more resilient and dependable. Like most other home automation systems, Domoticz does support MQTT.

There is no provision for things going wrong in the firmware. One problem has already been identified: the firmware will hang if the DHT20 cannot be initialized for some reason. Getting temperature readings is not the main function of the Wi-Fi switch so it is inexcusable to disable the device if an auxiliary function cannot be done for whatever reason. What happens if the Wi-Fi connection is lost? What happens if the Wi-Fi connection is never established in the first place because the network credentials are incorrect or had to be changed at the router? There is no provision for updating the firmware, where really, over-the-air firmware should be supported.

Using the template substitution engine of ESPAsyncWebServer along with the content refresh meta tag does work, but it is not perfect by any means. For one thing, the web interface flashes each time the page is reloaded. In order to mitigate that problem, a longer delay between reloads might be chosen, with the consequence that the web interface is less responsive. Even a relatively short five second delay before the Web interface is updated seems long when the LED is toggled with the physical switch.

The list of things to do to approach what Tasmota does is long, but I think the next bit to do will be to find a better way to update the data displayed on client Web browsers.



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有